package personal.jinhui.paprika.ui;

import java.io.IOException;
import java.io.InputStream;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jivesoftware.smack.packet.Presence.Mode;
import org.jivesoftware.smack.util.StringUtils;

import personal.jinhui.paprika.PaprikaApplication;
import personal.jinhui.paprika.providers.AvatarProvider;
import personal.jinhui.paprika.providers.MessagesHelper;
import personal.jinhui.paprika.service.Contact;
import personal.jinhui.paprika.service.Message;
import personal.jinhui.paprika.service.PresenceAdapter;
import personal.jinhui.paprika.service.UserInfo;
import personal.jinhui.paprika.utils.MessageText;
import personal.jinhui.paprika.utils.PaprikaBroadcastReceiver;
import personal.jinhui.paprika.utils.Status;

import android.app.Activity;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.LayerDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.preference.PreferenceManager;
import android.text.util.Linkify;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import personal.jinhui.paprika.R;
import personal.jinhui.paprika.service.aidl.IPaprikaRosterListener;
import personal.jinhui.paprika.service.aidl.IChat;
import personal.jinhui.paprika.service.aidl.IChatManager;
import personal.jinhui.paprika.service.aidl.IChatListener;
import personal.jinhui.paprika.service.aidl.IMessageListener;
import personal.jinhui.paprika.service.aidl.IRoster;
import personal.jinhui.paprika.service.aidl.IXmppFacade;

/**
 * This class represents an activity which allows the user to chat with his/her
 * contacts.
 */
public class ChatActivity extends Activity implements
		TextView.OnEditorActionListener {

	private static final String TAG = "ChatActivity";
	private static final Intent SERVICE_INTENT = new Intent();
	static {
		SERVICE_INTENT.setComponent(new ComponentName(
				"personal.jinhui.paprika",
				"personal.jinhui.paprika.PaprikaService"));
	}

	private Handler mHandler = new Handler();
	private IRoster mRoster;
	private Contact mContact;

	private TextView mContactNameTextView;
	private TextView mContactStatusMsgTextView;
	private TextView mContactChatState;
	private ImageView mContactStatusIcon;
	private LayerDrawable mAvatarStatusDrawable;
	private ListView mMessagesListView;
	private EditText mInputField;
	private Button mSendButton;
	private final Map<Integer, Bitmap> mStatusIconsMap = new HashMap<Integer, Bitmap>();

	private final List<MessageText> mListMessages = new ArrayList<MessageText>();

	private IChat mChat;
	private IChatManager mChatManager;
	private final IMessageListener mMessageListener = new MessageListener();
	private final IChatListener mChatManagerListener = new ChatListener();
	private MessagesListAdapter mMessagesListAdapter = new MessagesListAdapter();

	private final ServiceConnection mConn = new PaprikaServiceConnection();
	private final PaprikaBroadcastReceiver mBroadcastReceiver = new PaprikaBroadcastReceiver();
	private final PaprikaRosterListener mPaprikaRosterListener = new PaprikaRosterListener();
	private IXmppFacade mXmppFacade;
	private UserInfo mUserInfo;
	private String mCurrentAvatarId;
	private boolean mBinded;
	private boolean mCompact;

	/**
	 * Constructor.
	 */
	public ChatActivity() {
		super();
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	protected void onCreate(Bundle savedBundle) {
		super.onCreate(savedBundle);
		this.registerReceiver(mBroadcastReceiver, new IntentFilter(
				PaprikaBroadcastReceiver.Paprika_CONNECTION_CLOSED));
		SharedPreferences settings = PreferenceManager
				.getDefaultSharedPreferences(this);
		mCompact = settings.getBoolean(
				PaprikaApplication.USE_COMPACT_CHAT_UI_KEY, false);
		// UI
		if (!mCompact) { // compact chat is never used.
			setContentView(R.layout.chat);
			mContactNameTextView = (TextView) findViewById(R.id.chat_contact_name);
			mContactStatusMsgTextView = (TextView) findViewById(R.id.chat_contact_status_msg);
			mContactChatState = (TextView) findViewById(R.id.chat_contact_chat_state);
			mContactStatusIcon = (ImageView) findViewById(R.id.chat_contact_status_icon);
			mAvatarStatusDrawable = (LayerDrawable) mContactStatusIcon
					.getDrawable();
			mAvatarStatusDrawable.setLayerInset(1, 36, 36, 0, 0);
		} else {
			setContentView(R.layout.chat_compact);
		}
		mMessagesListView = (ListView) findViewById(R.id.chat_messages);
		mMessagesListView.setAdapter(mMessagesListAdapter);
		mInputField = (EditText) findViewById(R.id.chat_input);
		mInputField.setOnEditorActionListener(this);
		mInputField.requestFocus();
		mSendButton = (Button) findViewById(R.id.chat_send_message);
		mSendButton.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				sendMessage();
			}
		});

		prepareIconsStatus();
	}

	@Override
	protected void onResume() {
		super.onResume();
		if (!mBinded) {
			bindService(SERVICE_INTENT, mConn, BIND_AUTO_CREATE);
			mBinded = true;
		}
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	protected void onDestroy() {
		super.onDestroy();
		this.unregisterReceiver(mBroadcastReceiver);
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	protected void onPause() {
		super.onPause();
		try {
			if (mChat != null) {
				mChat.setOpen(false);
				mChat.removeMessageListener(mMessageListener);
			}
			if (mRoster != null)
				mRoster.removeRosterListener(mPaprikaRosterListener);
		} catch (RemoteException e) {
			Log.e(TAG, e.getMessage());
		}
		if (mBinded) {
			unbindService(mConn);
			mBinded = false;
		}
		mXmppFacade = null;
		mRoster = null;
		mChat = null;
		mChatManager = null;
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	protected void onNewIntent(Intent intent) {
		super.onNewIntent(intent);
		setIntent(intent);
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	protected void onSaveInstanceState(Bundle savedInstanceState) {
		super.onSaveInstanceState(savedInstanceState);
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		super.onRestoreInstanceState(savedInstanceState);
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	public final boolean onCreateOptionsMenu(Menu menu) {
		super.onCreateOptionsMenu(menu);
		MenuInflater inflater = getMenuInflater();
		inflater.inflate(R.menu.chat, menu);
		return true;
	}

	/**
	 * {@inheritDoc}.
	 */
	@Override
	public final boolean onOptionsItemSelected(MenuItem item) {
		switch (item.getItemId()) {
		case R.id.chat_menu_close_chat:
			try {
				mChatManager.destroyChat(mChat);
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}
			this.finish();
			break;
		case R.id.chat_menu_message_history:
			break;
		default:
			return false;
		}
		return true;
	}

	/**
	 * Get all messages from the current chat and refresh the activity with
	 * them.
	 * 
	 * @throws RemoteException
	 *             If a Binder remote-invocation error occurred.
	 */
	private void playRegisteredTranscript() throws RemoteException {
		mListMessages.clear();
		if (mChat != null) {
			List<MessageText> msgList = convertMessagesList(mChat.getMessages());			
			mListMessages.addAll(msgList);
			mMessagesListAdapter.notifyDataSetChanged();
		}
	}

	private void updateContactMessages() throws RemoteException {
		mListMessages.clear();
		MessagesHelper mHelper = new MessagesHelper(ChatActivity.this);
		List<MessageText> msgList = mHelper.getMessages(mUserInfo.getJid());
		mListMessages.addAll(msgList);
		mMessagesListAdapter.notifyDataSetChanged();		
	}
	
	/**
	 * Convert a list of Message coming from the service to a list of
	 * MessageText that can be displayed in UI.
	 * 
	 * @param chatMessages
	 *            the list of Message
	 * @return a list of message that can be displayed.
	 */
	private List<MessageText> convertMessagesList(List<Message> chatMessages) {
		List<MessageText> result = new ArrayList<MessageText>(
				chatMessages.size());
		String remoteName = mContact.getName();
		String localName = getString(R.string.chat_self);
		MessageText lastMessage = null;	
		
		for (Message m : chatMessages) {
			String name = remoteName;
			String fromBareJid = StringUtils.parseBareAddress(m.getFrom());
			if (m.getType() == Message.MSG_TYPE_ERROR) {
				lastMessage = null;
				result.add(new MessageText(fromBareJid, name, m.getBody(),
						true, m.getTimestamp()));
			} else if (m.getType() == Message.MSG_TYPE_INFO) {
				lastMessage = new MessageText("", "", m.getBody(), false);
				result.add(lastMessage);

			} else if (m.getType() == Message.MSG_TYPE_CHAT) {
				if (fromBareJid == null) { // no from or from == yours
					name = localName;
					fromBareJid = "";
				}

				if (m.getBody() != null) {
					if (lastMessage == null
							|| !fromBareJid.equals(lastMessage.getBareJid())) {
						lastMessage = new MessageText(fromBareJid, name,
								m.getBody(), false, m.getTimestamp());
						result.add(lastMessage);
					} else {
						lastMessage.setMessage(lastMessage.getMessage().concat(
								"\n" + m.getBody()));
					}
				}
			}
		}
		return result;
	}

	/**
	 * {@inheritDoc}.
	 */
	private final class PaprikaServiceConnection implements ServiceConnection {

		/**
		 * Constructor.
		 */
		public PaprikaServiceConnection() {
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onServiceConnected(ComponentName name, IBinder service) {
			mXmppFacade = IXmppFacade.Stub.asInterface(service);
			try {
				mUserInfo = mXmppFacade.getUserInfo();
				mRoster = mXmppFacade.getRoster();
				if (mRoster != null){
					mRoster.addRosterListener(mPaprikaRosterListener);
					mContact = new Contact(getIntent().getData());
				}
				mChatManager = mXmppFacade.getChatManager();
				if (mChatManager != null) {
					if (mChat != null) {
						mChat.setOpen(false);
						mChat.removeMessageListener(mMessageListener);
					}
					mChat = mChatManager.getChat(mContact);

					Log.d("#######", "key is "+mContact.getJIDWithRes());
					
					if (mChat != null) {
						mChat.setOpen(true);
						mChat.addMessageListener(mMessageListener);
						mChatManager.deleteChatNotification(mChat);
					}

					updateContactInformations();
					updateContactStatusIcon();
					updateContactMessages();
					//playRegisteredTranscript();

				}
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onServiceDisconnected(ComponentName name) {
			mXmppFacade = null;
			try {
				mRoster.removeRosterListener(mPaprikaRosterListener);
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}
		}
	}

	/**
	 * {@inheritDoc}.
	 */
	private class PaprikaRosterListener extends IPaprikaRosterListener.Stub {

		/**
		 * Constructor.
		 */
		public PaprikaRosterListener() {
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onEntriesAdded(List<String> addresses)
				throws RemoteException {
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onEntriesDeleted(List<String> addresses)
				throws RemoteException {
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onEntriesUpdated(List<String> addresses)
				throws RemoteException {
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void onPresenceChanged(final PresenceAdapter presence)
				throws RemoteException {
			if (mContact.getJID().equals(
					StringUtils.parseBareAddress(presence.getFrom()))) {
				mHandler.post(new Runnable() {
					@Override
					public void run() {
						mContact.setStatus(presence.getStatus());
						mContact.setMsgState(presence.getStatusText());
						updateContactInformations();
						updateContactStatusIcon();
					}
				});
			}
		}
	}

	/**
	 * {@inheritDoc}.
	 */
	private class MessageListener extends IMessageListener.Stub {

		/**
		 * Constructor.
		 */
		public MessageListener() {
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void processMessage(IChat iChat, final Message msg)
				throws RemoteException {
			final String fromBareJid = StringUtils.parseBareAddress(msg
					.getFrom());

			if (mContact.getJID().equals(fromBareJid)) {
				mHandler.post(new Runnable() {

					@Override
					public void run() {
						if (msg.getType() == Message.MSG_TYPE_ERROR) {
							mListMessages.add(new MessageText(fromBareJid,
									mContact.getName(), msg.getBody(), true,
									msg.getTimestamp()));
							mMessagesListAdapter.notifyDataSetChanged();
						} else if (msg.getBody() != null) {
							MessageText lastMessage = null;
							if (mListMessages.size() != 0)
								lastMessage = mListMessages.get(mListMessages
										.size() - 1);

							if (lastMessage != null
									&& lastMessage.getBareJid().equals(
											fromBareJid)) {
								lastMessage.setMessage(lastMessage.getMessage()
										.concat("\n" + msg.getBody()));
								lastMessage.setTimestamp(msg.getTimestamp());
								mListMessages.set(mListMessages.size() - 1,
										lastMessage);
							} else if (msg.getBody() != null)
								mListMessages.add(new MessageText(fromBareJid,
										mContact.getName(), msg.getBody(),
										false, msg.getTimestamp()));
							mMessagesListAdapter.notifyDataSetChanged();
						}
					}
				});
			}
		}

		/**
		 * {@inheritDoc}.
		 */
		@Override
		public void stateChanged(IChat iChat) throws RemoteException {
			final String state = iChat.getState();
			mHandler.post(new Runnable() {
				@Override
				public void run() {
					String text = null;
					if ("active".equals(state)) {
						text = ChatActivity.this
								.getString(R.string.chat_state_active);
					} else if ("composing".equals(state)) {
						text = ChatActivity.this
								.getString(R.string.chat_state_composing);
					} else if ("gone".equals(state)) {
						text = ChatActivity.this
								.getString(R.string.chat_state_gone);
					} else if ("inactive".equals(state)) {
						text = ChatActivity.this
								.getString(R.string.chat_state_inactive);
					} else if ("paused".equals(state)) {
						text = ChatActivity.this
								.getString(R.string.chat_state_active);
					}
					if (!mCompact)
						mContactChatState.setText(text);
				}
			});

		}
	}

	/**
	 * Update the contact informations.
	 */
	private void updateContactInformations() {
		// Check for a contact name update
		String name = mContact.getName();
		String res = mContact.getSelectedRes();
		if (!mCompact) {
			if (!(mContactNameTextView.getText().toString().equals(name)))
				mContactNameTextView.setText(name);
			// Check for a contact status message update
			if (!(mContactStatusMsgTextView.getText().toString()
					.equals(mContact.getMsgState()))) {
				mContactStatusMsgTextView.setText(mContact.getMsgState());
				Linkify.addLinks(mContactStatusMsgTextView, Linkify.WEB_URLS);
			}
		} else {
			Mode m = Status.getPresenceModeFromStatus(mContact.getStatus());
			if (m == null)
				setTitle(getString(R.string.chat_name) + " " + name + " ("
						+ getString(R.string.contact_status_msg_offline) + ")");
			else
				setTitle(getString(R.string.chat_name) + " " + name + " ("
						+ m.name() + ")");
		}
	}

	/**
	 * Update the contact status icon.
	 */
	private void updateContactStatusIcon() {
		if (mCompact)
			return;
		String id = mContact.getAvatarId();
		if (id == null)
			id = "";
		Log.d(TAG, "update contact icon  : " + id);
		if (!id.equals(mCurrentAvatarId)) {
			Drawable avatar = getAvatarDrawable(mContact.getAvatarId());
			mAvatarStatusDrawable.setDrawableByLayerId(R.id.avatar, avatar);
			mCurrentAvatarId = id;
		}
		mContactStatusIcon.setImageLevel(mContact.getStatus());
	}

	/**
	 * Get a Drawable containing the avatar icon.
	 * 
	 * @param avatarId
	 *            the avatar id to retrieve or null to get default
	 * @return a Drawable
	 */
	private Drawable getAvatarDrawable(String avatarId) {
		Drawable avatarDrawable = null;
		if (avatarId != null) {
			Uri uri = AvatarProvider.CONTENT_URI.buildUpon()
					.appendPath(avatarId).build();
			InputStream in = null;
			try {
				try {
					in = getContentResolver().openInputStream(uri);
					avatarDrawable = Drawable.createFromStream(in, avatarId);
				} finally {
					if (in != null)
						in.close();
				}
			} catch (IOException e) {
				Log.w(TAG, "Error while setting the avatar", e);
			}
		}
		if (avatarDrawable == null)
			avatarDrawable = getResources().getDrawable(R.drawable.default_man);
		return avatarDrawable;
	}

	/**
	 * Prepare the status icons map.
	 */
	private void prepareIconsStatus() {
		BitmapFactory.Options options = new BitmapFactory.Options();
		mStatusIconsMap.put(Status.CONTACT_STATUS_AVAILABLE, BitmapFactory
				.decodeResource(getResources(),
						android.R.drawable.presence_online, options));

		mStatusIconsMap.put(Status.CONTACT_STATUS_AVAILABLE_FOR_CHAT,
				BitmapFactory.decodeResource(getResources(),
						android.R.drawable.presence_online, options));
		mStatusIconsMap.put(Status.CONTACT_STATUS_AWAY, BitmapFactory
				.decodeResource(getResources(),
						android.R.drawable.presence_away, options));
		mStatusIconsMap.put(Status.CONTACT_STATUS_BUSY, BitmapFactory
				.decodeResource(getResources(),
						android.R.drawable.presence_busy, options));
		mStatusIconsMap.put(Status.CONTACT_STATUS_DISCONNECT, BitmapFactory
				.decodeResource(getResources(),
						android.R.drawable.presence_offline, options));
		mStatusIconsMap.put(Status.CONTACT_STATUS_UNAVAILABLE, BitmapFactory
				.decodeResource(getResources(), R.drawable.status_requested,
						options));
	}

	/**
	 * {@inheritDoc}.
	 */
	private class MessagesListAdapter extends BaseAdapter {

		/**
		 * Constructor.
		 */
		public MessagesListAdapter() {
		}

		/**
		 * Returns the number of messages contained in the messages list.
		 * 
		 * @return The number of messages contained in the messages list.
		 */
		@Override
		public int getCount() {
			return mListMessages.size();
		}

		/**
		 * Return an item from the messages list that is positioned at the
		 * position passed by parameter.
		 * 
		 * @param position
		 *            The position of the requested item.
		 * @return The item from the messages list at the requested position.
		 */
		@Override
		public Object getItem(int position) {
			return mListMessages.get(position);
		}

		/**
		 * Return the id of an item from the messages list that is positioned at
		 * the position passed by parameter.
		 * 
		 * @param position
		 *            The position of the requested item.
		 * @return The id of an item from the messages list at the requested
		 *         position.
		 */
		@Override
		public long getItemId(int position) {
			return position;
		}

		/**
		 * Return the view of an item from the messages list.
		 * 
		 * @param position
		 *            The position of the requested item.
		 * @param convertView
		 *            The old view to reuse if possible.
		 * @param parent
		 *            The parent that this view will eventually be attached to.
		 * @return A View corresponding to the data at the specified position.
		 */
		public View getView(int position, View convertView, ViewGroup parent) {
			View sv;
			if (convertView == null) {
				LayoutInflater inflater = ChatActivity.this.getLayoutInflater();
				sv = inflater.inflate(R.layout.chat_msg_row, null);
			} else {
				sv = convertView;
			}
			DateFormat df = DateFormat.getDateTimeInstance(DateFormat.SHORT,
					DateFormat.MEDIUM);
			MessageText msg = mListMessages.get(position);
			TextView msgName = (TextView) sv.findViewById(R.id.chatmessagename);
			msgName.setText(msg.getName());
			msgName.setTextColor(Color.WHITE);
			msgName.setError(null);
			TextView msgText = (TextView) sv.findViewById(R.id.chatmessagetext);
			msgText.setText(msg.getMessage());
			registerForContextMenu(msgText);
			TextView msgDate = (TextView) sv.findViewById(R.id.chatmessagedate);
			if (msg.getTimestamp() != null) {
				String date = df.format(msg.getTimestamp());
				msgDate.setText(date);
			}
			if (msg.isError()) {
				String err = getString(R.string.chat_error);
				msgName.setText(err);
				msgName.setTextColor(Color.RED);
				msgName.setError(err);
			}
			return sv;
		}
	}



	/**
	 * {@inheritDoc}.
	 */
	@Override
	public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
		if (v == mInputField && actionId == EditorInfo.IME_ACTION_SEND) {
			sendMessage();
			return true;
		}
		return false;
	}

	/**
	 * Send an XMPP message.
	 */
	private void sendMessage() {
		final String inputContent = mInputField.getText().toString();

		if (!"".equals(inputContent)) {
			Message msgToSend = new Message(mContact.getJIDWithRes(),
					Message.MSG_TYPE_CHAT);
			msgToSend.setBody(inputContent);

			try {
				if (mChat == null) {
					mChat = mChatManager.createChat(mContact, mMessageListener);
					mChat.setOpen(true);
				}
				mChat.sendMessage(msgToSend);
			} catch (RemoteException e) {
				Log.e(TAG, e.getMessage());
			}

			final String self = getString(R.string.chat_self);
			MessageText lastMessage = null;
			if (mListMessages.size() != 0)
				lastMessage = mListMessages.get(mListMessages.size() - 1);

			if (lastMessage != null && lastMessage.getName().equals(self)) {
				lastMessage.setMessage(lastMessage.getMessage().concat(
						"\n" + inputContent));
				lastMessage.setTimestamp(new Date());
			} else
				mListMessages.add(new MessageText(self, self, inputContent,
						false, new Date()));
			mMessagesListAdapter.notifyDataSetChanged();
			mInputField.setText(null);
		}
	}

	private class ChatListener extends IChatListener.Stub {

		/**
		 * Constructor.
		 */
		public ChatListener() {
		}

		@Override
		public void onChatCreated(IChat iChat, boolean locally) {
		}

		@Override
		public void onProcessMessage(IChat iChat, Message message)
				throws RemoteException {
			// TODO Auto-generated method stub
			
		}
	}
}
